#include "Subsystems/SubsystemBodies.h"
#include "Subsystems/SubsystemTimeOfDay.h"

#include "Player/PlayerData.h"
#include "Game/BodyRaycastResult.hpp"
#include "Components/ComponentBody.h"
#include "Components/ComponentPlayer.h"
#include "Network/PackageManager.h"
#include "Network/Packages/SubsystemBodyPackage.h"

#include "GameSingleton.h"
#include "GameRegistry.hpp"
#include <cfloat>

namespace PocketSurvival
{
    /**
     * 区域表的半径大小，这里的半径指的是正方形的中点到边的最短距离，而不是圆的半径
    */
    static const float findRadius = 8.0f;
    
    /**
     * @brief 给定位置，返回半径内的所有区域里面的 Body组件
     * @param point 区域的中心点
     * @param radius 检查的半径
     * @param result 结果会存放到该容器里面
    */
    void SubsystemBodies::findBodiesAroundPoint(const Vector2 &point, float radius, std::vector<ComponentBody*> &result)
    {
		int32_t num =  (int32_t)MathUtils::Floor<float>((point.posX - radius) / findRadius);
		int32_t num2 = (int32_t)MathUtils::Floor<float>((point.posY - radius) / findRadius);
		int32_t num3 = (int32_t)MathUtils::Floor<float>((point.posX + radius) / findRadius);
		int32_t num4 = (int32_t)MathUtils::Floor<float>((point.posY + radius) / findRadius);

        std::lock_guard<std::mutex> lk(m_bodiesMutex);

		for (int32_t i = num; i <= num3; i++)
		{
			for (int32_t j = num2; j <= num4; j++)
			{
                auto findIt = m_componentBodiesByArea.find(Point2(i, j).toKey());
                if(findIt != m_componentBodiesByArea.end())
                {
                    for(auto it = findIt->second.begin(); it != findIt->second.end(); ++it)
                    {
                        result.push_back(*it);
                    }
                }
			}
		}
    }
    
    /**
     * @brief 给定位置，返回半径内的所有区域里面的 Body组件
     * @param point 区域的中心点
     * @param radius 检查的半径
     * @param result 结果会存放到该容器里面
    */
    void SubsystemBodies::findBodiesAroundPoint(const Vector2 &point, float radius, std::unordered_set<ComponentBody*> &result)
    {
		int32_t num =  (int32_t)MathUtils::Floor<float>((point.posX - radius) / findRadius);
		int32_t num2 = (int32_t)MathUtils::Floor<float>((point.posY - radius) / findRadius);
		int32_t num3 = (int32_t)MathUtils::Floor<float>((point.posX + radius) / findRadius);
		int32_t num4 = (int32_t)MathUtils::Floor<float>((point.posY + radius) / findRadius);

        std::lock_guard<std::mutex> lk(m_bodiesMutex);

		for (int32_t i = num; i <= num3; i++)
		{
			for (int32_t j = num2; j <= num4; j++)
			{
                auto findIt = m_componentBodiesByArea.find(Point2(i, j).toKey());
                if(findIt != m_componentBodiesByArea.end())
                {
                    for(auto it = findIt->second.begin(); it != findIt->second.end(); ++it)
                    {
                        result.insert(*it);
                    }
                }
			}
		}
    }

    /**
     * @brief 给定区间，返回其中所有区域里面的 Body组件
     * @param corner1 起始点
     * @param corner2 末尾点(2点会组成一个矩形)
     * @param result 结果会存放到该容器里面
    */
    void SubsystemBodies::findBodiesInArea(const Vector2 &corner1, const Vector2 &corner2, std::vector<ComponentBody*> &result)
    {
		Point2 point ( (int32_t)MathUtils::Floor<float>(corner1.posX / findRadius), (int32_t)MathUtils::Floor<float>(corner1.posY / findRadius) );
		Point2 point2( (int32_t)MathUtils::Floor<float>(corner2.posX / findRadius), (int32_t)MathUtils::Floor<float>(corner2.posY / findRadius) );
		int32_t num = MathUtils::Min<int32_t>(point.posX, point2.posX) - 1;
		int32_t num2 = MathUtils::Min<int32_t>(point.posY, point2.posY) - 1;
		int32_t num3 = MathUtils::Max<int32_t>(point.posX, point2.posX) + 1;
		int32_t num4 = MathUtils::Max<int32_t>(point.posY, point2.posY) + 1;

        std::lock_guard<std::mutex> lk(m_bodiesMutex);

		for (int32_t i = num; i <= num3; i++)
		{
			for (int32_t j = num2; j <= num4; j++)
			{
                auto findIt = m_componentBodiesByArea.find(Point2(i, j).toKey());
                if(findIt != m_componentBodiesByArea.end())
                {
                    for(auto it = findIt->second.begin(); it != findIt->second.end(); ++it)
                    {
                        result.push_back(*it);
                    }
                }
			}
		}
    }
    std::shared_ptr<BodyRaycastResult> SubsystemBodies::raycast(const Vector3 &start, const Vector3 &end, float inflateAmount, std::function<bool(ComponentBody*, float)> func)
    {
		float num = Vector3::Distance(start, end);
		Vector2 corner(start.posX, start.posZ);
		Vector2 corner2(end.posX, end.posZ);

		std::shared_ptr<BodyRaycastResult> bodyRaycastResult = std::make_shared<BodyRaycastResult>();
        bodyRaycastResult->ray.Position = start;
        bodyRaycastResult->ray.Direction = (num > 0.0f) ? ((end - start) / num) : FastVector3::UnitX;
        bodyRaycastResult->componentBody = nullptr;
        bodyRaycastResult->distance = FLT_MAX;

        std::vector<ComponentBody*> componentBodies;
        findBodiesInArea(corner, corner2, componentBodies);
        for(int32_t index = 0; index < componentBodies.size(); ++index)
        {
            float num2 = 0.0;
            BoundingBox box = componentBodies[index]->getBoundingBox();
            if(inflateAmount > 0.0f)
            {
                box.Min -= inflateAmount;
                box.Max += inflateAmount;
            }
            if(bodyRaycastResult->ray.Intersection(box, num2) == true && num2 < num && num2 < bodyRaycastResult->distance && func(componentBodies[index], num2) == true)
            {
                bodyRaycastResult->distance = num2;
                bodyRaycastResult->componentBody = componentBodies[index];
            }
        }
        if(bodyRaycastResult->componentBody == nullptr)
        {
            return nullptr;
        }
        return bodyRaycastResult;
    }

    void SubsystemBodies::update(float dt)
    {
        if (m_timeOfDay->periodicGameTimeEvent(1.0, 0.0))
        {
            m_bodiesMutex.lock();

            m_componentBodiesByArea.clear();
            // m_bodyKeyMap.clear(); // 这个就不用清理了，updateBody会修改body所出在哪个区域
            for(auto it = m_bodyKeyMap.begin(); it != m_bodyKeyMap.end(); ++it)
            {
                updateBody(it->first);
            }

            m_bodiesMutex.unlock();
        }

        /*
        // 发送动物位置给客户端
        if (m_timeOfDay->periodicGameTimeEvent(0.1, 0.0))
        {
            std::unordered_set<ComponentBody*> comBodySet;

            auto view = GameSingleton::gameRegistry->view<ComponentPlayer*>();
            for(entt::entity entity : view)
            {
                ComponentPlayer* player = view.get<ComponentPlayer*>(entity);
                comBodySet.clear();

                Vector3 position;
                player->componentBody->getPosition(position);

                findBodiesAroundPoint(Vector2(position.posX, position.posZ), 64.0f, comBodySet);
                // 若Body是玩家自己的话，是不用发送的
                auto it = comBodySet.find(player->componentBody);
                if(it != comBodySet.end())
                {
                    comBodySet.erase(it);
                }
                std::shared_ptr<SubsystemBodyPackage> package = std::make_shared<SubsystemBodyPackage>(comBodySet);
                package->sendType = PackageSendType::TARGET;
                package->client = player->playerData->client;
                GameSingleton::packageManager->addSendPackageToQueue(package);
            }
        }
        */

        // 发送动物位置给客户端
        if (m_timeOfDay->periodicGameTimeEvent(0.1, 0.0))
        {
            auto view = GameSingleton::gameRegistry->view<ComponentBody*>();
            std::vector<ComponentBody*> comBodyVec;
            for (entt::entity entity : view)
            {
                ComponentBody *body = view.get<ComponentBody*>(entity);
                if (body->componentPlayer == nullptr)
                {
                    comBodyVec.push_back(body);
                }
            }
            std::shared_ptr<SubsystemBodyPackage> package = std::make_shared<SubsystemBodyPackage>(comBodyVec);
            package->sendType = PackageSendType::ALL;
            GameSingleton::packageManager->addSendPackageToQueue(package);
        }
    }

    /**
     * @brief 计算 Body组件 所处的区域，这个函数是private函数，只有在 update 中调用到，在调用前会清理掉了所有的区域记录,然后在进行所在区域的计算。锁已经在 update 中锁好了，这里不需要额外加锁
     * @param componentBody Body组件
    */
    void SubsystemBodies::updateBody(ComponentBody *componentBody)
    {
        Vector3 position;
        componentBody->getPosition(position);

        Point2 point( (int32_t)MathUtils::Floor<float>(position.posX / findRadius), (int32_t)MathUtils::Floor<float>(position.posZ / findRadius));
        int64_t key = point.toKey();
        auto areaIt = m_componentBodiesByArea.find(key);
        if(areaIt != m_componentBodiesByArea.end())
        {
            areaIt->second.insert(componentBody);
        }
        else
        {
            m_componentBodiesByArea[key] = std::unordered_set<ComponentBody*>{ componentBody };
        }
        m_bodyKeyMap[componentBody] = key;
    }


    void SubsystemBodies::onEntityAdded(entt::entity entity)
    {
        std::lock_guard<std::mutex> lk(m_bodiesMutex);
        ComponentBody **componentBodyPtr = GameSingleton::gameRegistry->try_get<ComponentBody*>(entity);
        if(componentBodyPtr != nullptr)
        {
            updateBody(*componentBodyPtr);
        }
    }
    
    void SubsystemBodies::onEntityRemoved(entt::entity entity)
    {
        std::lock_guard<std::mutex> lk(m_bodiesMutex);
        ComponentBody **componentBodyPtr = GameSingleton::gameRegistry->try_get<ComponentBody*>(entity);
        if(componentBodyPtr != nullptr)
        {
            auto findKeyIt = m_bodyKeyMap.find(*componentBodyPtr);
            if(findKeyIt != m_bodyKeyMap.end())
            {
                int64_t key = findKeyIt->second;
                auto findBodyIt = m_componentBodiesByArea[key].find((*componentBodyPtr));
                m_componentBodiesByArea[key].erase(findBodyIt);
                m_bodyKeyMap.erase(findKeyIt);
            }
        }
    }

    void SubsystemBodies::load(const nlohmann::json& json)
    {
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
    }

    static const std::string subsystemName = "Bodies";
    const std::string& SubsystemBodies::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemBodies::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }

} // namespace PocketSurvival
